<script setup lang="ts">
import { onClickOutside, useClipboardItems } from '@vueuse/core'
import { message } from 'ant-design-vue'
import MarkdownIt from 'markdown-it'

type UserMessageItem = {
    role: "user"
    content: string
    aborted?: boolean
    [key: string]: any
}
type AssistantMessageItem = {
    role: "assistant"
    content: string
    aborted?: boolean
    related: string
    [key: string]: any
}
type MessageItem = UserMessageItem | AssistantMessageItem;
defineOptions({
    name: 'GpChatView'
})
const props = defineProps<{
  chatId?: number
}>()
useEffectScope(() => {
  watch(() => props.chatId, () => {
    handleNewChat()
  })
})
// 自定义插件来处理 ::card::
function cardPlugin(md: MarkdownIt) {
    const startRegex = /^----card-start$/;
    const endRegex = /^----card-end$/;
    const cardRegex = /^::card::\|(.*)\|(.*)\|(.*)\|(.*)$/;

    md.block.ruler.before(
      'paragraph',
      'card-section',
      function (state, startLine, endLine, silent) {
        let pos = state.bMarks[startLine] + state.tShift[startLine];
        let max = state.eMarks[startLine];
        let line = state.src.slice(pos, max);

        if (!startRegex.test(line)) {
          return false;
        }

        if (silent) {
          return true;
        }

        let nextLine = startLine + 1;
        let foundEnd = false;
        const cards: any = [];
        while (nextLine < endLine) {
          pos = state.bMarks[nextLine] + state.tShift[nextLine];
          max = state.eMarks[nextLine];
          line = state.src.slice(pos, max);
          if (endRegex.test(line)) {
            foundEnd = true;
            break;
          }
          const match = line.match(cardRegex);
          if (match) {
            cards.push({
              title: match[1].trim(),
              image: match[2].trim(),
              description: match[3].trim(),
              id: match[4].trim(),
            });
          }
          nextLine++;
        }

        if (!foundEnd) {
          return false;
        }

        const token = state.push('card-section', 'div', 1);
        token.block = true;
        token.markup = '----card-start';
        token.map = [startLine, nextLine + 1];
        token.cards = cards;

        state.line = nextLine + 1;
        return true;
      }
    );

    md.renderer.rules['card-section'] = function (tokens, idx, options, env, self) {
      const token = tokens[idx];
      const cards = token.cards;
      let result = '<div class="card-section">';
      for (const card of cards) {
        result += `<a class="card" href="/case/${card.id}" target="_blank">`;
        result += `<img src="${card.image}" alt="${card.title}" class="card-image">`;
        result += '<div class="card-info">';
        result += `<h3 class="card-title">${card.title}</h3>`;
        result += `<p class="card-description">${card.description}</p>`;
        result += `</div>`;
        result += '</a>';
      }
      result += '</div>';
      return result;
    };
  }
const md = new MarkdownIt({ html: true, breaks: true, typographer: true })
md.use(cardPlugin);

const messages = ref<MessageItem[]>([])
const userInput = ref('')
const isLoading = ref(false)
const isThinking = ref(false)
const apiKey = ref('')
const apiEndpoint = ref('')

const model = ref<{ id: string; name: string; }>({} as any)

const currentTypingMessage = ref<string>("")
const currentReasoningContent = ref<string>("")
const messageContainer = ref<INullable<HTMLDivElement>>(null)

const session_id = ref(null);

const controller = ref<INullable<AbortController>>(null)
let sendId = 0
function resumeAbortState() {
  aborted = false
  continuing = false
  readOver = false
  continueChunks.length = 0
  stopAutoScroll = false
  stopContinueTimer()
}
async function sendMessage(msg = '') {
  if (msg) {
    userInput.value = msg
  }

  if (!userInput.value.trim() || isLoading.value)
    return

  const _sendId = ++sendId

  // 添加用户消息
  messages.value.push({
    role: 'user',
    content: userInput.value.trim(),
  })

  userInput.value = ''
  isLoading.value = true
  isThinking.value = true

  resumeAbortState()
  console.log('重新发送')
  // 添加助手消息占位
  const assistantMessage = reactive<AssistantMessageItem>({
    role: 'assistant',
    content: '',
    related: messages.value[messages.value.length - 1].content
  })
// @ts-ignore
  messages.value.push(assistantMessage)
  await nextTick()
  scrollToBottom()

  try {
    currentTypingMessage.value = ''
    currentReasoningContent.value = ''
    await callDeepseekAPI(assistantMessage)
  }
  catch (error) {
    console.error('Error:', error)
    // assistantMessage.content = "抱歉，发生了错误，请稍后重试。";
  }
  finally {
    console.log('finally', _sendId, sendId, isLoading.value)
    if (_sendId === sendId) {
      isLoading.value = false
      nextTick(() => {
        scrollToBottom()
      })
    }
  }
}
let aborted = false
let continuing = false
let readOver = false
const continueChunks: Array<{ content: string }> = []
let continueTimer: INullable<number> = null
const stopHint = '已暂停生成。'
function onStop() {
  // 在这里停止 completions 聊天
  if (controller.value) {
    // controller.value.abort()
    aborted = true
    stopContinueTimer()
    isLoading.value = false
    if (
      messages.value.length > 0
      && messages.value[messages.value.length - 1].role === 'assistant'
    ) {
      // 思考中中断
      if (!currentTypingMessage.value.trim()) {
        currentTypingMessage.value += stopHint
        messages.value[messages.value.length - 1].content
          = currentTypingMessage.value
        messages.value[messages.value.length - 1].aborted = true
      }
      else {
        // 如果消息已开始但尚未完成，添加提示信息
        // currentTypingMessage.value += '\n\n_[消息已中断]_'
        // messages.value[messages.value.length - 1].content
        //   = currentTypingMessage.value
        messages.value[messages.value.length - 1].aborted = true
      }
    }
  }
}

async function callDeepseekAPI(assistantMessage: AssistantMessageItem) {
  if (!apiKey.value) {
    throw new Error('API密钥未配置')
  }
  const _sendId = sendId
  controller.value?.abort?.()
  // 创建新的 AbortController
  controller.value = new AbortController()
  const response = await fetch(`${apiEndpoint.value}/chat/completions`, {
    method: 'POST',
    headers: {
      'Content-Type': 'application/json',
      'Authorization': `Bearer ${apiKey.value}`,
    },
    body: JSON.stringify({
      model: model.value.id, // deepseek-reasoner // deepseek-r1
      messages: messages.value.map(msg => ({
        role: msg.role,
        content: msg.content,
      })),
      stream: true,
      chatId: props.chatId,
      useResume: true
    }),
    signal: controller.value.signal,
  })

  if (!response.ok) {
    throw new Error(`API请求失败: ${response.status}`)
  }

  const reader = response.body!.getReader()
  const decoder = new TextDecoder()

  try {
    isThinking.value = false
    let reading = true
    while (reading) {
      // 重新发送了 取消接收上次
      if (sendId !== _sendId) {
        console.log('重新发送了 取消接收上次')
        resumeAbortState()
        currentTypingMessage.value = ''
        currentReasoningContent.value = ''
        break
      }
      const { done, value } = await reader.read()
      if (done) {
        reading = false
        readOver = true
        break
      }

      const chunk = decoder.decode(value)
      const lines = chunk.split('\n').filter(line => line.trim() !== '')

      for (const line of lines) {
        if (line.startsWith('data: ')) {
          const data = line.slice(6)
          if (data === '[DONE]')
            continue

          try {
            const parsed = JSON.parse(data)
            const content = parsed.choices[0]?.delta?.content || ''
            if (parsed.choices[0]?.delta?.session_id) {
              session_id.value = parsed.choices[0]?.delta?.session_id;
            }
            if (!assistantMessage.requestId && parsed.choices[0]?.delta?.request_id) {
              assistantMessage.requestId = parsed.choices[0]?.delta?.request_id
            }
            const reasoningContent
              = parsed.choices[0]?.delta?.reasoning_content || ''
            if (content || reasoningContent) {
              if (content) {
                if (!aborted) {
                  assistantMessage.content += content
                  currentTypingMessage.value += content
                } else {
                  const drained = continueChunks.length === 0
                  continueChunks.push({ content })
                  console.log('重新发送了 还在接收上次')
                  if (drained && continuing) {
                    handleContinue()
                  }
                }
              }
              if (reasoningContent) {
                if (!assistantMessage.reasoning_content) {
                  assistantMessage.reasoning_content = ''
                }
                assistantMessage.reasoning_content += reasoningContent
                currentReasoningContent.value += reasoningContent
              }
              await nextTick()
              scrollToBottom()
            }
          }
          catch (e) {
            console.error('解析响应数据失败:', e)
          }
        }
      }
    }
  }
  finally {
    reader.releaseLock()
  }
}

function scrollToBottom(force = false) {
  if (stopAutoScroll && !force) return
  if (force) {
    scroll()
    stopAutoScroll = false
    return
  }
  const typingStoped = aborted && !isLoading.value
  if (!typingStoped) {
    scroll()
  }
  function scroll() {
    if (messageContainer.value) {
      messageContainer.value.scrollTop = messageContainer.value.scrollHeight
    }
  }
}

onMounted(() => {
  apiKey.value = getStorageToken()
  apiEndpoint.value = getSideBarApiURL()
})
const { contextMenuPosition, contextMenuRef, handleContext, showContextMenu, closeMenu } = useContextMenu({
  shouldTrigger(el) {
    const isImg = el instanceof Image
    return isImg && !!el.closest('.answer-content')
  },
  beforeShow(el) {
    contextMenuImg.value = (el as HTMLImageElement).currentSrc
  },
})
useEffectScope(() => {
  watch(messageContainer, (_a, _b, onCleanup) => {
    if (messageContainer.value) {
      messageContainer.value.addEventListener('scroll', bindScroll, {
        passive: true
      })
      messageContainer.value.addEventListener('click', handleMessageClick)
      messageContainer.value.addEventListener('contextmenu', handleContext)
      onCleanup(() => {
        messageContainer.value?.removeEventListener('scroll', bindScroll)
        messageContainer.value?.removeEventListener('click', handleMessageClick)
        messageContainer.value?.removeEventListener('contextmenu', handleContext)
      })
    }
  })
})

const inputContent = ref("")
const hasInputContent = computed(() => !!inputContent.value)
const noMessages = computed(() => messages.value.length <= 0)
const wantToQuestions = ref([
  { content: '分析当前候选人匹配度' },
  { content: '基于当前候选人设计面试题' },
])
const showScrollKit = ref(false)
let lastScrollTop = 0;
let stopAutoScroll = false;
const handleScroll = throttle(100, function (ev: any) {
  const currentScrollTop = ev.target.scrollTop;
  const dis = Math.abs(currentScrollTop - lastScrollTop)
  if (currentScrollTop > lastScrollTop) {
    console.log('向 下 滚动');
    const maxScrollHeight = ev.target.scrollHeight - ev.target.offsetHeight
    if (maxScrollHeight - currentScrollTop < 50) {
      if (stopAutoScroll) {
        console.log('要恢复自动滚动')
        stopAutoScroll = false
      }
    }
  } else if (currentScrollTop < lastScrollTop) {
    console.log('向 上 滚动');
    if (dis > 100) {
      if (!stopAutoScroll) {
        console.log('要停止自动滚动')
        stopAutoScroll = true
      }
    }
  }

  lastScrollTop = currentScrollTop;
});
function bindScroll(ev: any) {
  // if (isLoading.value) {
  //   showScrollKit.value = false
  //   return
  // }
  const distance = ev.target.scrollHeight - ev.target.offsetHeight - ev.target.scrollTop
  showScrollKit.value = distance > 200
  handleScroll(ev)
  closeContextMenu()
}
function handleMessage() {
  const message = inputContent.value.trim();
  if (message !== '') {
    if (import.meta.env.DEV) {
      console.log('发送的消息:', message);
    }
    sendMessage(message)
    inputContent.value = '';
  }
}
const messageActions = ref([
  {
    family: "iconfont",
    key: 'copy',
    icon: 'icon-fuzhi',
    title: '复制',
  },
  {
    family: "iconfont",
    key: 'regenerate',
    icon: 'icon-zhongxinshengcheng',
    title: '重新生成',
  },
  {
    family: "iconfont",
    key: 'like',
    icon: 'icon-xihuan',
    title: '喜欢',
  },
  {
    family: "iconfont",
    key: 'dislike',
    icon: 'icon-buxihuan',
    title: '不喜欢',
  },
])
const { copy } = useCopy()
function handleClickMessageAction(message: any, key: string) {
  if (key === 'copy') {
    if (message.content) {
      const htmlCode = md.render(message.content)
      const el = document.createElement('div')
      el.innerHTML = htmlCode
      copy(el.textContent!)
    }
  } else if (key === 'regenerate') {
    messages.value.pop()
    messages.value.pop()
    sendMessage(message.related)
  } else if (key === 'like') {
    message.liked = !message.liked
    message.disliked = false
  } else if (key === 'dislike') {
    message.liked = false
    message.disliked = !message.disliked
  } else if (key === 'continue') {
    stopContinueTimer()
    handleContinue()
  }
}
function stopContinueTimer() {
  clearTimeout(continueTimer!)
  continueTimer = null
}
function endContinueWhenReadOver() {
  if (continuing && readOver && continueChunks.length <= 0) {
    isLoading.value = false
    continuing = false
    messages.value[messages.value.length - 1].aborted = false
  }
}
function sanitizeAbortHint() {
  if (currentTypingMessage.value.startsWith(stopHint)) {
    currentTypingMessage.value = currentTypingMessage.value.replace(stopHint, "")
  }
  if (messages.value[messages.value.length - 1].content.startsWith(stopHint)) {
    messages.value[messages.value.length - 1].content = messages.value[messages.value.length - 1].content.replace(stopHint, "")
  }
}
function handleContinue() {
  if (aborted) {
    continuing = true
    if (continueChunks.length > 0) {
      isLoading.value = true
      const content = continueChunks.shift()!.content
      sanitizeAbortHint()
      currentTypingMessage.value += content
      messages.value[messages.value.length - 1].content += content
      if (continueChunks.length > 0) {
        continueTimer = setTimeout(handleContinue, 300);
      }
      // 当流读完 此时是前端typing 触发滚动到最底部
      if (readOver) {
        nextTick(scrollToBottom)
      }
    }
    endContinueWhenReadOver()
  }
}
function resetScrollState() {
  showScrollKit.value = false
  lastScrollTop = 0
  stopAutoScroll = false
}
function handleNewChat() {
  resumeAbortState()
  resetScrollState()
  isLoading.value = false
  messages.value.length = 0
}
function handleMessageClick(ev: MouseEvent) {
  const target = ev.target
  const isContent = target instanceof Element && target.closest('.answer-content')
  if (!isContent) return
  const isImg = target instanceof Image
  const isAnchor = target instanceof HTMLAnchorElement
  if (isImg) {
    handlePreview(target.currentSrc)
  } else if (isAnchor) {
    handleAnchor(ev)
  }
}
const router = useRouter()
function handleAnchor(ev: MouseEvent) {
  const target = ev.target as HTMLAnchorElement
  const docOrigin = new URL(window.location.href)
  const link = new URL(target.href, docOrigin.href)
  if (link.origin === docOrigin.origin) {
    ev.preventDefault()
    router.push([link.pathname, link.search, link.hash].join(''))
  } else {
    console.log('点击链接不同源: ', link.href)
    window.open(link.href, "_blank")
  }
}
function handlePreview(url: string) {
//   showImagePreview({
//     images: [url]
//   });
    console.log(url)
}
const { copy: copyImage, isSupported } = useClipboardItems()
const contextMenuImg = ref<string>("")
const contextMenus = ref([
  { key: 'copy', text: '复制' },
  { key: 'download', text: '下载' }
])
onClickOutside(contextMenuRef, closeContextMenu)
function closeContextMenu() {
  contextMenuImg.value = ""
  closeMenu()
}
async function handleContextMenuClick(key: string) {
  console.log('handleContextMenuClick: ', key, contextMenuImg.value)
  if (!contextMenuImg.value) return
  if (key === 'copy') {
    if (!isSupported.value) {
      message.warn('你所在环境尚不支持该操作')
      return
    }
    const blob = await getBlobFromUrl(contextMenuImg.value)
    copyImage([
      new ClipboardItem({
        'image/png': new Blob([blob], { type: 'image/png' }),
      })
    ]).then(() => {
      message.success('复制成功')
    }).catch(err => {
      message.error('复制失败')
      console.error(err)
    })
  } else if (key === 'download') {
    try {
      await saveAs(contextMenuImg.value)
      message.success('下载成功')
    } catch (error) {
      message.error('下载失败')
      console.error(error)
    }
  }
  closeContextMenu()
}

function handleActionClick() {
  if (isThinking.value || isLoading.value) {
    onStop()
  } else if (hasInputContent.value) {
    handleMessage()
  }
}
useSavedPosition(messageContainer)
</script>

<template>
  <div :class="['chat-container', noMessages ? 'no-messages' : '']">
    <!-- AI小智 -->
    <div class="h-12 box-border flex flex-row items-stretch px-[18px] bg-white absolute left-0 top-0 right-0 z-10">
      <div class="h-full leading-12 text-base font-bold text-theme border-b-[2px] border-solid border-b-theme">
        AI小智
      </div>
    </div>
    <!-- 欢迎界面 -->
    <div v-if="noMessages" class="flex-1 relative px-8 mt-[214px]">
      <!-- 欢迎语 -->
      <div class="box-border mt-[74px]">
        <img src="/images/robot-welcome-msg.png" alt="自我介绍" :width="160" :height="20"
          class="object-cover block w-[160px] h-5" />
        <div class="text-[#0D265B] font-700 mt-[6px] text-2xl">你的超级小助理已上线！</div>
      </div>
      <div class="block absolute right-0 top-0 z-0">
        <img src="/images/robot-welcome.png" alt="机器人" :width="188" :height="204"
            class="object-cover block w-[188px] h-[204px]" />
      </div>
      <!-- 试一下 -->
      <div class="bg-white shadow-[0_0_20px_0_#14237014] rounded-xl pt-6 px-4 pb-4 mt-5 relative">
        <div class="text-theme font-900 text-[18px] !leading-[18px]">试一下：</div>
        <div class="guess-list">
          <!-- item for loop -->
          <div class="guess-item" v-for="item of wantToQuestions" :key="item.content"
            @click="sendMessage(item.content)">
            <div class="flex-1 truncate font-medium text-sm">{{ item.content }}</div>
            <img src="/images/message-send.png" alt="发送" :width="26" :height="26" class="object-cover flex-none size-[26px]" />
          </div>
        </div>
      </div>
    </div>
    <!-- 消息容器 -->
    <div v-else class="chat-messages">
      <div ref="messageContainer" class="chat-messages-content">
        <div v-for="(message, index) in messages" :key="index" class="message" :class="message.role">
          <div v-if="message.role === 'assistant'" class="avatar">
            <img src="/images/ai-chat.png" alt="Assistant">
          </div>
          <div class="message-content-wrap">
            <div class="message-content" v-if="message.role === 'user'">
              {{ message.content }}
            </div>
            <div class="message-content" v-else>
              <!-- 最后一条消息 -->
              <div v-if="index === messages.length - 1">
                <!-- 思考中 -->
                <div class="thinking" v-if="isThinking && !message.content">
                  <div class="text-[#333333] text-sm mb-1">正在思考中</div>
                  <div class="flex flex-row justify-between">
                    <img src="/images/thinking.gif" alt="thinking" :width="20" :height="20"
                      class="size-5 block object-cover select-none" />
                    <!-- <a-tooltip title="中止">
                    <div class="stop-round" @click="onStop">
                      <div class="stop-inner"></div>
                    </div>
                  </a-tooltip> -->
                  </div>
                </div>
                <div v-else v-html="md.render(currentTypingMessage)" class="answer-content" />
                <!-- <span v-if="!isThinking && isLoading" class="relative size-6 flex">
                <span class="absolute h-full w-full inline-flex animate-ping rounded-full bg-gray-400 opacity-75" />
                <span class="relative size-6 inline-flex rounded-full bg-gray-500" />
              </span> -->
              </div>
              <!-- 以往消息 -->
              <div v-else>
                <div v-html="md.render(message.content)" class="answer-content" />
              </div>
              <!-- 操作 最后一条 -->
              <div class="flex flex-row gap-5 items-center pt-[10px]" v-if="index === messages.length - 1">
                <a-tooltip :title="item.title" v-for="item in messageActions" :key="item.key" v-if="!isLoading">
                  <GpFontIcon
                    :family="item.family"
                    :name="item.icon"
                    :class="['text-[#0D265B] !text-[20px] hover:text-theme cursor-pointer', item.key, message.liked ? 'liked' : '', message.disliked ? 'disliked' : '']"
                    @click="handleClickMessageAction(message, item.key)"
                  />
                </a-tooltip>
                <!-- <a-tooltip title="中止" v-if="!isThinking && isLoading">
                <div class="stop-round ml-auto" @click="onStop">
                  <div class="stop-inner"></div>
                </div>
              </a-tooltip> -->
                <div v-if="!isThinking && isLoading"></div>
                <div v-else-if="message.aborted"
                  class="text-[#515A6C] text-sm hover:text-theme cursor-pointer flex flex-row items-center ml-auto gap-1"
                  @click="handleClickMessageAction(message, 'continue')">
                  <div :class="['iconfont text-base icon-jixushengcheng']"></div>
                  <div>继续生成</div>
                </div>
              </div>
              <!-- 操作 非最后一条 -->
              <div class="flex flex-row gap-4 items-center pt-[10px]" hidden v-else>
                <a-tooltip :title="item.title" v-for="item in messageActions" :key="item.key">
                  <GpFontIcon
                    :family="item.family"
                    :name="item.icon"
                    :class="['text-[#0D265B] text-base hover:text-theme cursor-pointer', item.key, message.liked ? 'liked' : '', message.disliked ? 'disliked' : '']"
                    @click="handleClickMessageAction(message, item.key)"
                  />
                </a-tooltip>
                <div v-if="message.aborted"
                  class="text-[#515A6C] text-sm hover:text-theme cursor-pointer flex flex-row items-center ml-auto gap-1"
                  @click="handleClickMessageAction(message, 'continue')">
                  <div :class="['iconfont text-base icon-jixushengcheng']"></div>
                  <div>继续生成</div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>
      <!-- 新对话 -->
      <a-tooltip title="新对话" placement="left">
        <div class="new-chat-popover" @click="handleNewChat">
          <div class="iconfont icon-duihua text-[#636c7f] text-[20px]"></div>
        </div>
      </a-tooltip>
      <!-- 滚动到底部 -->
      <div :class="['scroll-toolkit', showScrollKit ? '!opacity-100' : '']" @click="scrollToBottom(true)">
        <div class="iconfont icon-zhidi1 text-[#636c7f] text-[20px]"></div>
      </div>
    </div>
    <!-- 底部搜索 -->
    <div class="bottom-action">
      <div class="bottom-send-wrap">
        <GpEditable v-model:content="inputContent" placeholder="输入问题，向小智提问" style="flex: 1;" @press-enter="handleMessage()"/>
        <!-- 小智 -->
        <div class="absolute top-[6px] left-[6px] size-fit">
          <img src="/images/ai-chat.png" alt="小智" :width="28"
              :height="28"
              class="size-[28px] block select-none box-border object-cover" />
        </div>
        <!-- 发送/停止按钮 -->
        <div class="absolute right-[6px] bottom-[6px]" @click="handleActionClick">
          <a-tooltip title="中止" v-if="isThinking || isLoading">
            <div class="stop-round ml-auto">
              <div class="stop-inner"></div>
            </div>
          </a-tooltip>
          <div v-else :class="hasInputContent ? 'cursor-pointer' : 'cursor-not-allowed'"
            class="flex flex-row justify-center items-center box-border size-[28px] rounded-full bg-linear-90 from-theme from-0% to-[#54C3FA] to-100%"
          >
            <img src="/images/send-chat.png" alt="发送" :width="14"
              :height="14"
              class="size-[14px] block select-none box-border object-cover" />
          </div>
        </div>
      </div>
    </div>
    <!-- 图片右键菜单 -->
    <div class="context-menu" v-show="showContextMenu" ref="contextMenuRef"
      :style="{ left: contextMenuPosition.x + 'px', top: contextMenuPosition.y + 'px' }">
      <div v-for="item of contextMenus" :key="item.key" class="context-menu-item"
        @click="handleContextMenuClick(item.key)">{{ item.text }}</div>
    </div>
  </div>
</template>

<style lang="less" scoped>
.chat-container {
  display: flex;
  flex-direction: column;
  height: 100%;
  max-width: 960px;
  margin: 0 auto;
  box-sizing: border-box;
  width: 100%;
  font-family: -apple-system, 'PingFang SC';
  position: relative;
  
  &.no-messages {
    background-image: linear-gradient(135.8deg, #f0f7ff 0%, #eff5fb 63%, #f3eef8 100%);
    background-repeat: no-repeat;
    background-size: 100% 100%;
  }
}

.guess-list {
  box-sizing: border-box;
  margin-top: 6px;

  .guess-item {
    display: flex;
    flex-direction: row;
    align-items: center;
    justify-content: space-between;
    padding: 10px 0;
    cursor: pointer;
    color: #0D265B;
    border-bottom: 1px solid #F2F6FD;

    &:hover {
      color: var(--color-theme);
    }

    &:last-child {
      border-bottom: none;
    }
  }
}

.chat-messages {
  flex: 1;
  overflow: hidden;
  box-sizing: border-box;
  position: relative;
}

.chat-messages-content {
  background: #F9FAFB;
  overflow-y: auto;
  box-sizing: border-box;
  padding: 80px 16px 0 12px;
  height: 100%;
}

.new-chat-popover {
  position: absolute;
  top: 8px;
  right: 16px;
  cursor: pointer;
  border-radius: 8px;
  border: 1px solid #FFF;
  background: #F7F8FA;
  box-shadow: 0 2px 8px 0 #0b265114;
  padding: 6px;

  &:hover {
    .iconfont {
      color: var(--color-theme);
    }
  }
}

.scroll-toolkit {
  position: absolute;
  bottom: 10px;
  right: 16px;
  width: 24px;
  height: 24px;
  border-radius: 50%;
  background-color: #fff;
  border: 1px solid #768AAC;
  cursor: pointer;
  display: flex;
  justify-content: center;
  align-items: center;
  opacity: 0;
  transition: opacity .3s;

  &:hover {
    background-color: #E0EAFA;
  }
}

.message {
  margin-bottom: 24px;
  display: flex;
  flex-direction: row;
  align-items: flex-start;
  gap: 8px;
}

.message.user {
  justify-content: flex-end;
}

.avatar {
  width: 24px;
  height: 24px;
  border-radius: 50%;
  overflow: hidden;
  flex: none;
}

.avatar img {
  width: 100%;
  height: 100%;
  object-fit: cover;
  display: block;
  user-select: none;
}

.message-content-wrap {
  flex: 1;
  width: 0;
}

.message-content {
  max-width: 100%;
  word-wrap: break-word;
  word-break: break-word;
}

.user .message-content {
  width: fit-content;
  margin-left: auto;
  background: #0c67fe1a;
  color: #333333;
  font-size: 14px;
  line-height: 20px;
  padding: 10px 12px;
  border-radius: 20px 0 20px 20px;
}

.assistant .message-content-wrap {
  box-sizing: border-box;
  // margin-top: 5px;
}

.assistant .message-content {
  box-sizing: border-box;
}

.answer-content {
  color: #333333;
  font-size: 14px;
  font-weight: 400;
  line-height: 20px;
}

.bottom-action {
  padding: 10px 16px 20px;
  flex: none;
  border-top: 1px solid #FFFFFF;

  .bottom-send-wrap {
    width: 100%;
    height: 40px;
    box-sizing: border-box;
    border-radius: 20px;
    border: 1px solid #0768fa80;
    padding: 6px 42px;
    position: relative;
    display: flex;
    flex-direction: row;
    align-items: center;
    caret-color: var(--color-theme);
    background-color: #ffffff;
  }
}

.stop-round {
  border-radius: 50%;
  width: 28px;
  height: 28px;
  box-sizing: border-box;
  border: 1px solid var(--color-theme);
  background-color: #ffffff;
  cursor: pointer;
  display: flex;
  flex-direction: row;
  justify-content: center;
  align-items: center;

  .stop-inner {
    width: 8px;
    height: 8px;
    background-color: var(--color-theme);
  }

  &:hover {
    background-color: var(--color-theme);

    .stop-inner {
      background-color: #ffffff;
    }
  }
}

.like.liked,
.dislike.disliked {
  color: var(--color-theme);
}

.context-menu {
  position: fixed;
  box-sizing: border-box;
  border-radius: 8px;
  background: #FFF;
  box-shadow: 0 0 10px 0 #14237014;
  padding: 0 4px;

  .context-menu-item {
    color: #0d265b;
    font-family: inherit;
    font-size: 14px;
    font-weight: 400;
    padding: 12px 16px;
    cursor: pointer;
    border-bottom: 1px solid #DDDDDD;

    &:hover {
      color: var(--color-theme);
    }

    &:last-child {
      border-bottom: none;
    }
  }
}
</style>
<style lang="less" scoped>
.message.assistant .answer-content {
  :deep(:first-child) {
    margin-top: 0 !important;
  }

  :deep(p) {
    margin-top: 0 !important;
    margin-bottom: 10px !important;
  }

  :deep(img) {
    width: auto;
    max-width: 100%;
  }

  :deep(pre) {
    overflow: scroll;
  }

  // :deep(h1) {
  //   font-size: 18px;
  //   font-weight: 700;
  // }

  // :deep(h2) {
  //   font-size: 18px;
  //   font-weight: 600;
  // }

  // :deep(h3),
  // :deep(h4),
  // :deep(h5),
  // :deep(h6) {
  //   font-size: 16px;
  //   font-weight: 600;
  // }

  // :deep(p) {
  //   font-size: 16px;
  //   font-weight: 400;
  // }

  // :deep(strong) {
  //   font-size: 14px;
  //   font-weight: 500;
  // }

  // :deep(li) {
  //   font-size: 14px;
  //   font-weight: 400;
  // }
}
</style>
<style lang="less" scoped>
.message.assistant .answer-content {
  --rem: 16px;
  --text-primary: #333333;
  /* 主文本色 */
  --text-secondary: #666666;
  /* 次级文本 */
  --accent-color: #2d3748;
  /* 强调色 */

  --code-bg: #f6f8fa;
  /* 代码块背景 */

  --border-color: #e2e8f0;
  /* 分割线/引用线 */
  /* 主文本色 */
  line-height: 1.6;
  color: var(--text-primary);
  font-size: var(--rem);
  /* ===== 标题层级 ===== */

  :deep(h1) {

    font-size: calc(1.5 * var(--rem));
    /* 24px */

    font-weight: 700;

    // margin: calc(1.25 * var(--rem)) 0;
    /* 20px */

  }



  :deep(h2) {

    font-size: calc(1.25 * var(--rem));
    /* 20px */

    font-weight: 600;

    // margin: var(--rem) 0;
    /* 16px */

  }



  :deep(h3) {

    font-size: calc(1.125 * var(--rem));
    /* 18px */

    font-weight: 600;

    // margin: calc(0.75 * var(--rem)) 0;
    /* 12px */

  }



  :deep(h4),
  :deep(h5),
  :deep(h6) {

    font-size: var(--rem);
    /* 16px */

    font-weight: 600;

    color: var(--accent-color);

    // margin-left: calc(0.5 * var(--rem));
    /* 8px */

  }

  /* ===== 段落与强调 ===== */

  :deep(p) {

    font-size: calc(0.875 * var(--rem));
    /* 14px */

    margin: calc(0.75 * var(--rem)) 0;
    /* 12px */

    line-height: 1.6;

  }



  :deep(strong) {

    font-weight: 600;

    color: var(--accent-color);

    // background-color: #fff3b2;
    /* 可选高亮 */

  }



  :deep(em) {
    font-style: italic;
  }

  /* ===== 列表系统 ===== */

  :deep(ul),
  :deep(ol) {

    margin: calc(0.5 * var(--rem)) 0 calc(0.5 * var(--rem)) 3px;

    padding-left: var(--rem);
    /* 16px */

  }



  :deep(li) {

    font-size: calc(0.875 * var(--rem));

    margin: calc(0.5 * var(--rem)) 0;

    line-height: 1.5;
  }

  :deep(ul),
  :deep(ul > li) {
    list-style: disc;
  }

  :deep(ol),
  :deep(ol > li) {
    list-style: decimal;
  }

  // :deep(ul > li:has(ul)) {
  //   list-style: none;
  // }
  // :deep(ul > li:has(ol)) {
  //   list-style: none;
  // }
  :deep(li:only-of-type::marker) {
    display: none;
  }

  // :deep(ol > li:has(:where(ul, ol))) {
  //   list-style: none;
  // }

  /* 无序列表符号 */

  :deep(ul li::marker) {

    // content: "●";

    // font-size: calc(0.5 * var(--rem));
    // /* 8px */

    // vertical-align: middle;

  }

  /* ===== 代码块 ===== */

  :deep(pre) {

    background: var(--code-bg);

    padding: calc(0.75 * var(--rem));
    /* 12px */

    border-radius: 6px;

    overflow-x: auto;

    margin: var(--rem) 0;
    /* 16px */

  }



  :deep(code) {

    font-family: 'SFMono-Regular', Consolas, monospace;

    font-size: calc(0.875 * var(--rem));

  }

  /* 行内代码 */

  :deep(p code) {

    padding: calc(0.125 * var(--rem)) calc(0.25 * var(--rem));

    border-radius: 4px;

  }



  /* ===== 引用与分割线 ===== */

  :deep(blockquote) {

    border-left: 3px solid var(--border-color);

    margin: var(--rem) 0;

    padding: calc(0.5 * var(--rem)) var(--rem);

    color: var(--text-secondary);

  }


  :deep(hr) {
    
    border: 0;
    
    height: 1px;
    
    background: var(--border-color);
    
    margin: calc(1.5 * var(--rem)) 0;
    /* 24px */
    
  }
  
  
  /** 素材案例卡片 */
    :deep(a.card) {
      display: flex;
      width: 311px;
      border-radius: 8px;
      background: #fff;
      padding: 6px;
    }

    :deep(a.card .card-image) {
      width: 85px;
      height: 64px;
      border-radius: 8px;
      border: 1px solid #fff;
      margin-right: 6px;
    }
    :deep(.card-info) {
      flex: 1;
    }
    :deep(.card-title) {
      color: #333;
      font-family: 'PingFang SC';
      font-size: 14px;
      font-style: normal;
      font-weight: 900;
      line-height: 14px;
      margin: 6px 0;
      overflow: hidden;
      text-overflow: ellipsis;
      display: -webkit-box;
      line-clamp: 1;
      -webkit-line-clamp: 1;
      -webkit-box-orient: vertical;
    }

    :deep(.card-description) {
      color: #666;
      font-family: 'PingFang SC';
      font-size: 12px;
      font-style: normal;
      font-weight: 400;
      line-height: 16px;
      overflow: hidden;
      text-overflow: ellipsis;
      display: -webkit-box;
      line-clamp: 2;
      -webkit-line-clamp: 2;
      -webkit-box-orient: vertical;
      margin: 0;
    }

    :deep(.card-section) {
      display: flex;
      flex-wrap: wrap;
      gap: 8px;
    }

}

/* ===== 响应式适配 ===== */

@media (min-width: 769px) {

  /* 桌面端增强 */

  .message.assistant .answer-content {
    :deep(pre) {
      font-size: 14px;
    }
  }
}



@media (max-width: 480px) {

  /* 小屏手机优化 */
  .message.assistant .answer-content {

    // --rem: 13px;
    // font-size: var(--rem);
    :deep(h1) {
      font-size: calc(1.35 * var(--rem));
    }
  }
}
</style>